package org.dolphin.service.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.annotation.Resource;
import org.apache.commons.lang3.LocaleUtils;
import org.dolphin.base.bean.UserVO;
import org.dolphin.dao.AccessControlDao;
import org.dolphin.dao.GroupDao;
import org.dolphin.dao.UserDao;
import org.dolphin.entity.misc.UserLocale;
import org.dolphin.entity.security.AccessControl;
import org.dolphin.entity.security.Group;
import org.dolphin.entity.security.Url;
import org.dolphin.entity.security.User;
import org.dolphin.entity.security.UserGroup;
import org.dolphin.service.SecurityControlService;
import org.dolphin.service.base.CustomUser;
import org.dolphin.service.base.CustomUserDetails;
import org.dolphin.service.base.UserProfileHelper;
import org.dolphin.util.Pagination;
import org.hibernate.criterion.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.dao.SaltSource;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

@Service(value = "securityControlService")
@Transactional(readOnly = true, propagation = Propagation.REQUIRED)
public class SecurityControlServiceImpl implements SecurityControlService {
  @Autowired(required = true)
  private UserDao userDao;
  @Autowired(required = true)
  private GroupDao groupDao;
  @Autowired(required = true)
  private PasswordEncoder passwordEncoder;
  @Autowired(required = true)
  private AccessControlDao accessControlDao;
  private final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
  @Resource(name = "messageResource")
  private MessageSource messageSource;
  @Autowired(required = true)
  private SaltSource saltSource;

  @Transactional(readOnly = false)
  @Override
  public void changePassword(String currentPassword, String newPassword, String confirmedNewPassword) {
    Locale locale = UserProfileHelper.getUserLocale();
    if (!StringUtils.hasText(currentPassword) || !StringUtils.hasText(newPassword)
        || !StringUtils.hasText(confirmedNewPassword)) {
      String key = !StringUtils.hasText(currentPassword) ? "field.current.password" : !StringUtils
          .hasText(newPassword) ? "field.new.password" : "field.confirmed.new.password";
      String field = messageSource.getMessage(key, null, locale);
      String error = messageSource.getMessage("error.required", new Object[] {field}, locale);
      throw new IllegalArgumentException(error);
    }
    if (!ObjectUtils.nullSafeEquals(newPassword, confirmedNewPassword)) {
      String error = messageSource.getMessage("error.user.password.mismatch", null, locale);
      throw new IllegalArgumentException(error);
    }
    String id = UserProfileHelper.getUserIdentity();
    User user = userDao.fetchUserByUsername(id);
    String oldPassword = user.getPassword();
    String encodeCurrentPassword = encodePassword(currentPassword);
    if (!ObjectUtils.nullSafeEquals(oldPassword, encodeCurrentPassword)) {
      String error = messageSource.getMessage("error.user.password.mismatch", null, locale);
      throw new IllegalArgumentException(error);
    }

    String encodeNewPassword = encodePassword(newPassword);
    user.setPassword(encodeNewPassword);
    user.setInvalidAuthenticationCount(0);
    user.setLocked(Boolean.FALSE);
    userDao.updateUser(user);
  }

  private String constructLocaleString(String language, String country, String variant) {
    StringBuilder builder = new StringBuilder(15);
    builder.append(language);
    if (StringUtils.hasText(country)) {
      builder.append("_");
      builder.append(country);
    }
    if (StringUtils.hasText(variant)) {
      builder.append("_");
      builder.append(variant);
    }
    return builder.toString();
  }

  @Override
  @Transactional(readOnly = false)
  public void createGroup(Group group) {
    groupDao.createGroup(group);
  }

  @Override
  @Transactional(readOnly = false)
  public void createUser(User user) {
    String password = user.getPassword();
    String encodePassword = encodePassword(password);
    user.setPassword(encodePassword);
    userDao.saveUser(user);
  }

  private CustomUserDetails createUserDetails(User user, List<GrantedAuthority> authorities) {
    UserLocale userLocale = user.getUserLocale();
    Locale locale = Locale.US;
    if (userLocale != null) {
      String language = userLocale.getLanguage();
      String country = userLocale.getCountry();
      String variant = userLocale.getVariant();
      String localeString = constructLocaleString(language, country, variant);
      locale = LocaleUtils.toLocale(localeString);
    }
    String fullname = user.getFirstname()
        + (StringUtils.hasText(user.getLastname()) && !"-".equals(user.getLastname()) ? " "
            + user.getLastname() : "");
    return new CustomUser(user.getId(), fullname, user.getUsername(), user.getPassword(),
        user.getEnabled(), true, true, !user.getLocked(), authorities, locale);
  }

  protected String encodePassword(String plainPassword) {
    String encodePassword = passwordEncoder.encodePassword(plainPassword, saltSource);
    return encodePassword;
  }

  @Override
  public User fetchUser(Long id) {
    User user = userDao.fetchUser(id);
    return user;
  }

  @Override
  public List<Group> getAssignedGroups(String username) {
    return groupDao.getAssignedGroups(username);
  }

  @Override
  public List<Url> getSecurityMetadata() {
    List<Url> accessControls = accessControlDao.getUrlAccessControls();
    return accessControls;
  }

  @Override
  public List<UserVO> getUsers(int page, int rows, List<Order> orders) {
    page = page > 0 ? page : 1;
    int startIndex = Pagination.calculateStartIndex(page, rows);
    List<UserVO> users = userDao.getUsers(startIndex, rows, orders);
    return users;
  }

  @Override
  public Long getUsersCount() {
    Long records = userDao.getUserCount();
    return records;
  }

  @Override
  public List<Group> loadGroups(Set<String> groupName) {
    return groupDao.loadGroups(groupName);
  }

  @Override
  public User loadUser(String username) {
    return userDao.loadUser(username);
  }

  @Override
  public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    User user = userDao.fetchUserByUsername(username);
    if (user == null) {
      throw new UsernameNotFoundException(messages.getMessage("JdbcDaoImpl.notFound",
          new Object[] {username}));
    }
    Boolean administrator = user.getAdministrator();
    List<GrantedAuthority> authorities = Collections.emptyList();
    List<AccessControl> accessControls = Collections.emptyList();
    if (!administrator) {
      List<UserGroup> userGroups = user.getUserGroups();
      if (userGroups != null && !userGroups.isEmpty()) {
        Set<Group> groups = new HashSet<Group>(userGroups.size());
        for (UserGroup userGroup : userGroups) {
          Group group = userGroup.getGroup();
          groups.add(group);
        }
        accessControls = accessControlDao.getAccessControls(groups);
      }
    } else {
      accessControls = accessControlDao.getAccessControls();
    }
    if (accessControls != null && !accessControls.isEmpty()) {
      authorities = new ArrayList<GrantedAuthority>(accessControls.size());
      for (AccessControl ac : accessControls) {
        authorities.add(new SimpleGrantedAuthority(ac.getAuthority()));
      }
    }
    return createUserDetails(user, authorities);
  }

  @Transactional(readOnly = false)
  @Override
  public void updateUser(User user) {
    userDao.updateUser(user);
  }
}
